﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;

namespace NeuralNetworks
{
    public class Neuron
    {
        #region Constructors

        public Neuron()
        {
            _nextNeurons = null;
        }

        public Neuron(Neuron[] nextNeurons)
        {
            _nextNeurons = nextNeurons;
            this.CreateConnections();
        }

        #endregion

        #region Private Members

        private Neuron[] _nextNeurons;
        private float _sum = 0f;

        #endregion

        #region Attributes and Properties

        private static float _learningRate = 0.5f;
        public static float LearningRate
        {
            get { return _learningRate; }
            set { _learningRate = value; }
        }

        private NeuronConnection[] _connections;
        public NeuronConnection[] Connections
        {
            get { return _connections; }
        }

        private float _value;
        public float Value
        {
            get { return _value; }
        }

        private float _error;
        public float Error
        {
            get { return _error; }
        }

        private short _expectedValue;
        public short ExpectedValue
        {
            get { return _expectedValue; }
            set { _expectedValue = value; }
        }

        private bool _firstLayer = false;
        public bool FirstLayer
        {
            get { return _firstLayer; }
            set { _firstLayer = value; }
        }

        #endregion

        #region Private Methods

        private void CreateConnections()
        {
            _connections = new NeuronConnection[_nextNeurons.Count()];
            _connections.For((c, i) =>
                _connections[i] = new NeuronConnection(RandomGenerator.GenerateConnectionWeigth()));
        }

        #endregion

        #region Public Methods

        public void Add(NeuronConnection connection, float value)
        {
            if (connection == null)
                _sum += value;
            else
                _sum += value * connection.Weight;
        }

        public void CalculateValue()
        {
            if (_firstLayer)
                _value = _sum;
            else
                _value = (float)(1f / (1f + Math.Exp(-_sum)));

            _sum = 0f;
        }

        public void CalculateAndPropagateValue()
        {
            this.CalculateValue();

            if (_nextNeurons == null)
                return;

            _nextNeurons.For((neuron, i) =>
                neuron.Add(_connections[i], _value));
        }

        public void CalculateError()
        {
            float errorFactor = 0;

            if (_nextNeurons == null)
                errorFactor = _expectedValue - _value;
            else
            {
                _nextNeurons.For((neuron, i) =>
                    errorFactor += neuron.Error * _connections[i].Weight);
            }

            _error = _value * (1f - _value) * errorFactor;
        }

        public void UpdateWeights()
        {
            if (_connections == null)
                return;

            _connections.For((connection, i) =>
                connection.Weight += _learningRate * _value * _nextNeurons[i].Error);
        }

        #endregion

        #region Draw

        public const int DIAMETER = 40;
        private Font _font = new Font("Segoe UI", 8);

        private Point _position;
        public Point Position
        {
            get { return _position; }
            set { _position = value; }
        }

        public void Draw(Graphics g)
        {
            Rectangle rect = new Rectangle(_position, new Size(DIAMETER, DIAMETER));
            g.DrawEllipse(Pens.Black, rect);

            SizeF size = g.MeasureString(_value.ToString(), _font);
            g.DrawString(_value.ToString(), _font, Brushes.Black, rect.Left + DIAMETER / 2 - size.Width / 2, rect.Top + DIAMETER / 2 - size.Height / 2);

            if (_nextNeurons == null)
                return;

            Point start = new Point(rect.Right, rect.Top + DIAMETER / 2);
            _connections.For((connection, i) =>
            {
                Point end = new Point(_nextNeurons[i].Position.X, _nextNeurons[i].Position.Y + DIAMETER / 2);
                g.DrawLine(Pens.Black, start, end);

                float diffX = end.X - start.X;
                float diffY = end.Y - start.Y;
                PointF vector = new PointF(diffX, diffY);
                double sqX = Math.Pow(diffX, 2);
                double sqY = Math.Pow(diffY, 2);
                float length = (float)Math.Sqrt(sqX + sqY);
                float cos = (float)(diffX / length);
                float sin = (float)(diffY / length);

                SizeF textSize = g.MeasureString(connection.Weight.ToString(), _font);
                PointF textPosition = new PointF(start.X + (cos * length / 4f) - textSize.Width / 2f, start.Y + (sin * length / 4f) - textSize.Height / 2f);

                GraphicsState state = g.Save();

                g.TranslateTransform(textPosition.X + textSize.Width / 2f, textPosition.Y + textSize.Height / 2f);
                g.MultiplyTransform(new System.Drawing.Drawing2D.Matrix(cos, sin, -sin, cos, 0f, 0f));
                g.TranslateTransform(-textPosition.X - textSize.Width / 2f, -textPosition.Y - textSize.Height / 2f);

                g.DrawRectangle(Pens.Black, textPosition.X, textPosition.Y, textSize.Width, textSize.Height);
                g.FillRectangle(Brushes.White, new RectangleF(textPosition.X, textPosition.Y, textSize.Width, textSize.Height));
                g.DrawString(connection.Weight.ToString(), _font, Brushes.Green, textPosition);

                g.Restore(state);
            });
        }

        #endregion
    }
}
